package com.sunchangpeng.wechat.core;

import com.google.common.io.Files;
import com.sunchangpeng.wechat.core.exception.WechatClientException;
import com.sunchangpeng.wechat.core.exception.WechatError;
import com.sunchangpeng.wechat.core.exception.WechatServerException;
import com.sunchangpeng.wechat.core.http.HttpClient;
import com.sunchangpeng.wechat.core.http.HttpResponseWrapper;
import com.sunchangpeng.wechat.core.token.AccessToken;
import com.sunchangpeng.wechat.core.token.AccessTokenHolder;
import lombok.extern.slf4j.Slf4j;
import okhttp3.OkHttpClient;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import static com.sunchangpeng.wechat.core.http.HttpMediaType.TEXT_PLAIN;

@Slf4j
public class WechatClient extends HttpClient {
    private final String appId;
    private final String secret;
    private final AccessTokenHolder accessTokenHolder;

    public WechatClient(String appId, String secret, AccessTokenHolder accessTokenHolder) {
        this(new OkHttpClient(), appId, secret, accessTokenHolder);
    }

    public WechatClient(OkHttpClient httpClient, String appId, String secret, AccessTokenHolder accessTokenHolder) {
        super(httpClient);
        this.appId = appId;
        this.secret = secret;
        this.accessTokenHolder = accessTokenHolder;
    }

    public String getTextWithToken(String url) {
        return getTextWithToken(url, null);
    }

    public String getTextWithToken(String url, String json) {
        try {
            return getText(appendAccessToken(url), json);
        } catch (WechatServerException e) {
            if (invalidToken(e.getError().getErrorCode())) {
                log.warn("token invalid: {}, will refresh.", e.getError().getErrorCode());
                return getText(appendAccessToken(url), json);
            }
            throw e;
        }
    }

    public String getText(String url) {
        return WechatError.hasEror(http(url, null).getBodyText());
    }

    public String getText(String url, String json) {
        return WechatError.hasEror(http(url, json).getBodyText());
    }

    public byte[] getBytesWithToken(String url) {
        return getBytesWithToken(url, null);
    }

    public byte[] getBytesWithToken(String url, String json) {
        try {
            return getBytes(appendAccessToken(url), json);
        } catch (WechatServerException e) {
            if (invalidToken(e.getError().getErrorCode())) {
                log.warn("token invalid: {}, will refresh.", e.getError().getErrorCode());
                return getBytes(appendAccessToken(url), json);
            }
            throw e;
        }
    }

    public byte[] getBytes(String url) {
        return getBytes(url, null);
    }

    public byte[] getBytes(String url, String json) {
        HttpResponseWrapper responseWrapper = http(url, json);

        if (TEXT_PLAIN.type().equals(responseWrapper.contentType().type())) {
            WechatError.hasEror(responseWrapper.getBodyText());
            throw new WechatClientException(String.format("get bytes not support.[url=%s,json=%s]", url, json));
        }

        return responseWrapper.getBodyBytes();
    }


    public File getTempFileWithToken(String url) {
        return getTempFileWithToken(url, null);
    }

    public File getTempFileWithToken(String url, String json) {
        try {
            return getTempFile(appendAccessToken(url), json);
        } catch (WechatServerException e) {
            if (invalidToken(e.getError().getErrorCode())) {
                log.warn("token invalid: {}, will refresh.", e.getError().getErrorCode());
                return getTempFile(appendAccessToken(url), json);
            }
            throw e;
        }
    }

    public File getTempFile(String url) {
        return getTempFile(url, null);
    }

    public File getTempFile(String url, String json) {
        HttpResponseWrapper responseWrapper = http(url, json);
        String dispositionHeader = responseWrapper.header("Content-disposition");

        if (StringUtils.isBlank(dispositionHeader)) {
            if (TEXT_PLAIN.type().equals(responseWrapper.contentType().type())) {
                WechatError.hasEror(responseWrapper.getBodyText());
            }
            throw new WechatClientException(String.format("get file not support.[url=%s,json=%s]", url, json));
        }

        File tempFile = new File(Files.createTempDir(), extractFileName(dispositionHeader));
        try {
            Files.write(responseWrapper.getBodyBytes(), tempFile);
        } catch (IOException e) {
            throw new WechatClientException("write temp file error.", e);
        }
        return tempFile;
    }

    public String multiForm(String url, String name, String fileName, byte[] content) {
        return multiForm(url, name, fileName, content, null);
    }

    public String multiForm(String url, String name, String fileName, byte[] content, Map<String, String> form) {
        return WechatError.hasEror(http(url, name, fileName, content, form).getBodyText());
    }

    public String multiFormWithToken(String url, String name, String fileName, byte[] content) {
        return multiFormWithToken(url, name, fileName, content, null);
    }

    public String multiFormWithToken(String url, String name, String fileName, byte[] content, Map<String, String> form) {
        try {
            return multiForm(appendAccessToken(url), name, fileName, content, form);
        } catch (WechatServerException e) {
            if (invalidToken(e.getError().getErrorCode())) {
                log.warn("token invalid: {}, will refresh.", e.getError().getErrorCode());
                return multiForm(appendAccessToken(url), name, fileName, content, form);
            }
            throw e;
        }
    }

    public AccessToken getAccessToken() {
        return accessTokenHolder.accessToken();
    }

    private String appendAccessToken(String url) {
        String token = accessTokenHolder.accessToken().getAccessToken();
        return url + (url.indexOf('?') == -1 ? "?access_token=" + token : "&access_token=" + token);
    }

    private boolean invalidToken(int code) {
        boolean result = code == 42001 || code == 40001 || code == 40014;
        if (result) {
            accessTokenHolder.expireToken();//强制设置为无效
        }
        return result;
    }

    private String extractFileName(String headerValue) {
        String fileName = null;
        Pattern regex = Pattern.compile("(?<=filename=\").*?(?=\")");
        Matcher regexMatcher = regex.matcher(headerValue);
        if (regexMatcher.find()) {
            fileName = regexMatcher.group();
        }

        if (StringUtils.isBlank(fileName)) {
            log.warn("Cannot get filename from Content-disposition: {}", headerValue);
            fileName = UUID.randomUUID().toString();
        }

        return fileName;
    }

    public String getAppId() {
        return appId;
    }

    public String getSecret() {
        return secret;
    }
}
